# frozen_string_literal: true

require_relative '../../../spec_helper'
require 'wpxf/cli/module_cache'
require 'wpxf/modules'

describe Wpxf::Cli::ModuleCache do
  let :subject do
    Class.new do
      include Wpxf::Cli::ModuleCache

      def initialize
        super
      end
    end.new
  end

  before(:each, 'setup mocks') do
    allow(subject).to receive(:print_bad)
    allow(subject).to receive(:print_good)
    allow(subject).to receive(:print_warning)
    allow(subject).to receive(:print_info)
    allow(subject).to receive(:context)
    allow(subject).to receive(:reset_context_stack)
  end

  describe '#new' do
    it 'should initialise `current_version_number` with the gemspec version' do
      spec_path = File.join(Wpxf.app_path, 'wpxf.gemspec')
      gemspec = Gem::Specification.load(spec_path)
      expect(subject.current_version_number).to eql gemspec.version.to_s
    end
  end

  describe '#cache_valid?' do
    context 'if no version has been logged previously' do
      it 'should return false' do
        Wpxf::Models::Log.truncate
        expect(subject.cache_valid?).to be false
      end
    end

    context 'if the previously logged version is older than the current' do
      it 'should return false' do
        Wpxf::Models::Log.create(key: 'version', value: '1.0')
        subject.current_version_number = '1.1'
        expect(subject.cache_valid?).to be false
      end
    end

    context 'if the current version is equal to the logged version' do
      it 'should return true' do
        Wpxf::Models::Log.create(key: 'version', value: '1.0')
        subject.current_version_number = '1.0'
        expect(subject.cache_valid?).to be true
      end
    end

    context 'if the current version is lower than the logged version' do
      it 'should return false' do
        Wpxf::Models::Log.create(key: 'version', value: '1.2')
        subject.current_version_number = '1.1'
        expect(subject.cache_valid?).to be false
      end
    end
  end

  describe '#create_module_models' do
    context 'if `type` is exploit' do
      it 'should create a {Models::Module} for each class in the Wpxf::Exploit namespace' do
        modules = Wpxf::Exploit.constants.select do |c|
          Wpxf::Exploit.const_get(c).is_a? Class
        end

        subject.create_module_models 'exploit'
        exploit_count = Wpxf::Models::Module.where(type: 'exploit').count
        expect(exploit_count).to eql modules.count
      end
    end

    context 'if `type` is not exploit' do
      it 'should create a {Models::Module} for each class in the Wpxf::Exploit namespace' do
        modules = Wpxf::Auxiliary.constants.select do |c|
          Wpxf::Auxiliary.const_get(c).is_a? Class
        end

        subject.create_module_models 'auxiliary'
        exploit_count = Wpxf::Models::Module.where(type: 'auxiliary').count
        expect(exploit_count).to eql modules.count
      end
    end
  end

  describe '#refresh_version_log' do
    context 'if a version log already exists' do
      it 'should update the existing entry' do
        Wpxf::Models::Log.create(key: 'version', value: '5')
        expect(Wpxf::Models::Log.count).to eql 1

        subject.current_version_number = '99'
        subject.refresh_version_log
        expect(Wpxf::Models::Log.count).to eql 1

        log = Wpxf::Models::Log.first(key: 'version')
        expect(log).to_not be_nil
        expect(log.value.to_s).to eql '99'
      end
    end

    context 'if a version log does not exist' do
      it 'should create a new entry' do
        log = Wpxf::Models::Log.first(key: 'version')
        expect(log).to be_nil
        subject.current_version_number = '99'
        subject.refresh_version_log
        log = Wpxf::Models::Log.first(key: 'version')
        expect(log).to_not be_nil
        expect(log.value.to_s).to eql '99'
      end
    end
  end

  describe '#rebuild_cache' do
    it 'should warn the user the cache is being refreshed' do
      subject.rebuild_cache
      expect(subject).to have_received(:print_warning)
        .with('Refreshing the module cache...')
    end

    it 'should truncate the existing cache' do
      Wpxf::Models::Module.create(
        path: 'exploit/shell/test',
        name: 'test',
        type: 'exploit',
        class_name: 'Wpxf::Exploit::Test'
      )

      expect(Wpxf::Models::Module.first(name: 'test')).to_not be_nil
      subject.rebuild_cache
      expect(Wpxf::Models::Module.first(name: 'test')).to be_nil
    end

    it 'should reload the custom modules' do
      allow(Wpxf).to receive(:load_custom_modules).and_call_original
      subject.rebuild_cache
      expect(Wpxf).to have_received(:load_custom_modules).exactly(1).times
    end

    it 'should create a {Models::Module} for each class in the Wpxf::Exploit namespace' do
      modules = Wpxf::Exploit.constants.select do |c|
        Wpxf::Exploit.const_get(c).is_a? Class
      end

      subject.rebuild_cache
      exploit_count = Wpxf::Models::Module.where(type: 'exploit').count
      expect(exploit_count).to eql modules.count
    end

    it 'should create a {Models::Module} for each class in the Wpxf::Exploit namespace' do
      modules = Wpxf::Auxiliary.constants.select do |c|
        Wpxf::Auxiliary.const_get(c).is_a? Class
      end

      subject.rebuild_cache
      exploit_count = Wpxf::Models::Module.where(type: 'auxiliary').count
      expect(exploit_count).to eql modules.count
    end

    it 'should update the version log' do
      allow(subject).to receive(:refresh_version_log)
      subject.rebuild_cache
      expect(subject).to have_received(:refresh_version_log).exactly(1).times
    end

    it 'should reset the context stack' do
      subject.rebuild_cache
      expect(subject).to have_received(:reset_context_stack)
    end
  end
end
